using System;
using System.Collections.Generic;

namespace Mbs.ComponentModel
{
    public class UndoManager : DisposableObject
    {
        private static long seed = 0;

        [NonSerialized]
        private Transaction _tx;

        ActionStack actionStack;

        bool canAddAction = true;
        private int suspendCounter = 0;

        /// <summary>
        /// Changed event
        /// </summary>
        [field: NonSerialized]
        public event EventHandler Changed;

        [field: NonSerialized]
        public event EventHandler UndoAllCompleted;

       
        public long Id
        {
            get;
            private set;
        }

        protected virtual void OnChanged()
        {
            if (Changed != null)
                Changed(null, null);
        }

       
        //int actionCount;
        /// <summary>
        /// can undo
        /// </summary>
        public bool CanUndo
        {
            get { return canAddAction && actionStack.CanUndo; }
        }

        public void Resume()
        {
            suspendCounter--;
        }

        public void Suspend()
        {
            suspendCounter++;
        }

        public bool CanAddAction
        {
            get { return suspendCounter == 0 && actionStack.undoStack.Count < MaxCount; }
        }
        /// <summary>
        /// can redo
        /// </summary>
        public bool CanRedo
        {
            get { return suspendCounter == 0 && actionStack.CanRedo; }
        }

        public int UndoStackCount
        {
            get { return actionStack.undoStack.Count; }
        }

        public int RedoStackCount
        {
            get { return actionStack.redoStack.Count; }
        }

        public void MoveBack(IUndoAction item)
        {
            if (CanMoveBack(item))
            {
                if (actionStack.undoStack.Contains(item))
                    actionStack.undoStack.MoveBack(item);
                actionStack.redoStack.MoveBack(item);
            }
        }

        public void MoveForward(IUndoAction item)
        {
            if (CanMoveForward(item))
            {
                if (actionStack.undoStack.Contains(item))
                    actionStack.undoStack.MoveForward(item);
                actionStack.redoStack.MoveForward(item);
            }
        }

        public bool CanMoveBack(IUndoAction action)
        {
            if (actionStack.undoStack.CanMoveBack(action))
                return true;
            else if (actionStack.redoStack.CanMoveBack(action))
                return true;
            return false;
        }

        public bool CanMoveForward(IUndoAction action)
        {
            if (actionStack.undoStack.CanMoveForward(action))
                return true;
            else if (actionStack.redoStack.CanMoveForward(action))
                return true;
            return false;
        }


        /// <summary>
        /// push action
        /// </summary>
        /// <param name="action"></param>
        public void AddAction(IUndoAction action)
        {
            if (!CanAddAction)
                return;

            if (action == null)
                throw new ArgumentNullException("action == null");

            if (_tx != null)
                _tx.AddAction(action);
            else
            {
                actionStack.Push(action);
                OnChanged();
            }
        }

        public bool BeginTransaction(string transactionName)
        {
            if (!CanAddAction)
                return false;
            if (_tx == null)
                _tx = new Transaction(transactionName, actionStack);
            else
                _tx.Enlist();
            return true;
        }

        public void Commit()
        {
            if (suspendCounter > 0 || _tx == null)
                return;
            _tx.Commit();
            if (_tx.Status == TransactionStatus.Commited)
                OnChanged();
            if (_tx.transactionCount == 0)
                _tx = null;
        }

        public void Rollback()
        {
            if (suspendCounter > 0 || _tx == null)
                return;
            Suspend();
            _tx.Rollback();
            Resume();
            _tx = null;
        }

        private readonly int MaxCount;
        public UndoManager()
            : this(20)
        {
        }

        public UndoManager(int stackSize)
        {
            Id = ++seed;
            MaxCount = stackSize;
            actionStack = new ActionStack(stackSize);
        }

        static UndoManager _current;
        /// <summary>
        /// current service
        /// </summary>
        public static UndoManager Current
        {
            get
            {
                if (_current == null)
                    _current = new UndoManager();
                return _current;
            }
            set
            {
                if (_current != value)
                    _current = value;
            }
        }

        /// <summary>
        /// clear
        /// </summary>
        public void Clear()
        {
            actionStack.ClearAll();
            OnChanged();
        }

        /// <summary>
        /// perform undo action
        /// </summary>
        public void Undo()
        {
            Suspend();
            actionStack.Undo();
            Resume();
            OnChanged();
            if (UndoStackCount <= 0 && UndoAllCompleted != null)
                UndoAllCompleted(null, EventArgs.Empty);
        }

        /// <summary>
        /// perform redo action
        /// </summary>
        public void Redo()
        {
            Suspend();
            actionStack.Redo();
            Resume();
            OnChanged();
        }

        public IEnumerator<string> GetUndoEnumerator()
        {
            foreach (IUndoAction item in actionStack.undoStack)
                yield return item.Name;
        }

        public IEnumerator<string> GetRedoEnumerator()
        {
            foreach (IUndoAction item in actionStack.redoStack)
                yield return item.Name;
        }

        protected override void Dispose(bool disposing)
        {
            UndoAllCompleted = null;
            Changed = null;

            base.Dispose(disposing);
        }
    }
}
